/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "slow_runtime_helper.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/internal_call_params.h"
#include "plugins/ecmascript/runtime/interpreter/ecma-interpreter.h"
#include "plugins/ecmascript/runtime/interpreter/interpreter-inl.h"
#include "plugins/ecmascript/runtime/js_generator_object.h"
#include "plugins/ecmascript/runtime/js_invoker.h"
#include "plugins/ecmascript/runtime/js_tagged_value.h"
#include "plugins/ecmascript/runtime/tagged_array-inl.h"

namespace ark::ecmascript {

#ifdef PANDA_QEMU_AARCH64_GCC_8
// Build PANDA_QEMU_AARCH64_GCC_8 hangs without this workaround. Please check issue #8094.
JSTaggedValue GetGlobalObject(JSThread *thread)
{
    return thread->GetGlobalObject();
}
#endif

JSTaggedValue SlowRuntimeHelper::CallBoundFunction(EcmaRuntimeCallInfo *info)
{
    JSThread *thread = info->GetThread();
    JSHandle<JSBoundFunction> boundFunc(info->GetFunction());
    JSHandle<JSFunction> targetFunc(thread, boundFunc->GetBoundTarget());
    if (targetFunc->IsClassConstructor()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "class constructor cannot called without 'new'",
                                    JSTaggedValue::Exception());
    }

    JSHandle<TaggedArray> boundArgs(thread, boundFunc->GetBoundArguments());
    const uint32_t boundLength = boundArgs->GetLength();
    const uint32_t argsLength = info->GetArgsNumber();
    JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
    auto runtimeInfo = NewRuntimeCallInfo(thread, JSHandle<JSTaggedValue>(targetFunc), info->GetThis(), undefined,
                                          boundLength + argsLength);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    if (boundLength != 0) {
        // 0 ~ boundLength is boundArgs; boundLength ~ argsLength is args of EcmaRuntimeCallInfo.
        runtimeInfo->SetCallArg(boundLength, boundArgs->GetData());
    }
    if (argsLength != 0) {
        runtimeInfo->SetCallArg(
            argsLength, reinterpret_cast<JSTaggedType *>(info->GetArgAddress(js_method_args::NUM_MANDATORY_ARGS)),
            boundLength);
    }
    return EcmaInterpreter::Execute(runtimeInfo.Get());
}

JSTaggedValue SlowRuntimeHelper::NewObject(EcmaRuntimeCallInfo *info)
{
    JSThread *thread = info->GetThread();
    JSHandle<JSTaggedValue> func(info->GetFunction());
    if (!func->IsHeapObject()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "function is nullptr", JSTaggedValue::Exception());
    }

    if (!func->IsJSFunction()) {
        if (func->IsBoundFunction()) {
            JSTaggedValue result = JSBoundFunction::ConstructInternal(info);
            RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
            return result;
        }

        if (func->IsJSProxy()) {
            JSTaggedValue jsObj = JSProxy::ConstructInternal(info);
            RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
            return jsObj;
        }
        THROW_TYPE_ERROR_AND_RETURN(thread, "Constructed NonConstructable", JSTaggedValue::Exception());
    }

    JSTaggedValue result = JSFunction::ConstructInternal(info);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    return result;
}

void SlowRuntimeHelper::SaveFrameToContext(JSThread *thread, JSHandle<GeneratorContext> context)
{
    auto frame = thread->GetCurrentFrame();
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    uint32_t nregs = frame->GetSize();
    JSHandle<TaggedArray> regsArray = factory->NewTaggedArray(nregs);
    for (uint32_t i = 0; i < nregs; i++) {
        regsArray->Set(thread, i, VRegAsTaggedValue(frame->GetVReg(i)));
    }
    auto numVregs = frame->GetMethod()->GetNumVregs();
    auto func = VRegAsTaggedValue(frame->GetVReg(numVregs));
    auto acc = VRegAsTaggedValue(frame->GetAcc());

    context->SetRegsArray(thread, regsArray.GetTaggedValue());
    context->SetMethod(thread, func);

    context->SetAcc(thread, acc);
    context->SetNRegs(thread, JSTaggedValue(nregs));
    context->SetBCOffset(thread, JSTaggedValue(frame->GetBytecodeOffset()));
    context->SetLexicalEnv(thread, JSTaggedValue(JSFrame::GetJSEnv(frame)->GetLexicalEnv()));
}

JSTaggedValue ConstructGeneric(JSThread *thread, JSHandle<JSFunction> ctor, JSHandle<JSTaggedValue> newTgt,
                               JSHandle<JSTaggedValue> preArgs, uint32_t argsCount, JSTaggedType *stkargs)
{
    if (!ctor->IsConstructor()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "Constructor is false", JSTaggedValue::Exception());
    }

    JSHandle<JSTaggedValue> obj(thread, JSTaggedValue::Undefined());
    if (ctor->IsBase()) {
        ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
        obj = JSHandle<JSTaggedValue>(factory->NewJSObjectByConstructor(ctor, newTgt));
    }
    ASSERT(ctor->GetCallTarget());

    uint32_t preArgsSize = preArgs->IsUndefined() ? 0 : JSHandle<TaggedArray>::Cast(preArgs)->GetLength();
    const uint32_t size = preArgsSize + argsCount;
    auto info = NewRuntimeCallInfo(thread, ctor, obj, newTgt, size);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // add preArgs when boundfunction is encountered
    uint32_t argIdx = 0;
    if (preArgsSize > 0) {
        JSHandle<TaggedArray> tgaPreArgs = JSHandle<TaggedArray>::Cast(preArgs);
        for (uint32_t i = 0; i < preArgsSize; ++i) {
            info->SetCallArg(argIdx++, tgaPreArgs->Get(i));
        }
    }
    for (uint32_t i = 0; i < argsCount; ++i) {
        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
        info->SetCallArg(argIdx++, JSTaggedValue(stkargs[i]));
    }
    JSTaggedValue resultValue = EcmaInterpreter::Execute(info.Get());
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // 9.3.2 [[Construct]] (argumentsList, new_target)
    if (resultValue.IsECMAObject()) {
        return resultValue;
    }

    if (ctor->IsBase()) {
        return obj.GetTaggedValue();
    }
    if (!resultValue.IsUndefined()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "function is non-constructor", JSTaggedValue::Exception());
    }
    return obj.GetTaggedValue();
}

JSTaggedValue ConstructBoundFunction(JSThread *thread, JSHandle<JSBoundFunction> ctor, JSHandle<JSTaggedValue> newTgt,
                                     JSHandle<JSTaggedValue> preArgs, uint32_t argsCount, JSTaggedType *stkargs)
{
    JSHandle<JSTaggedValue> target(thread, ctor->GetBoundTarget());
    ASSERT(target->IsConstructor());

    JSHandle<TaggedArray> boundArgs(thread, ctor->GetBoundArguments());
    JSMutableHandle<JSTaggedValue> newPreArgs(thread, preArgs.GetTaggedValue());
    if (newPreArgs->IsUndefined()) {
        newPreArgs.Update(boundArgs.GetTaggedValue());
    } else {
        newPreArgs.Update(
            TaggedArray::Append(thread, boundArgs, JSHandle<TaggedArray>::Cast(preArgs)).GetTaggedValue());
    }
    JSMutableHandle<JSTaggedValue> newTargetMutable(thread, newTgt.GetTaggedValue());
    if (JSTaggedValue::SameValue(ctor.GetTaggedValue(), newTgt.GetTaggedValue())) {
        newTargetMutable.Update(target.GetTaggedValue());
    }
    return SlowRuntimeHelper::Construct(thread, target, newTargetMutable, newPreArgs, argsCount, stkargs);
}

JSTaggedValue ConstructProxy(JSThread *thread, JSHandle<JSProxy> ctor, JSHandle<JSTaggedValue> newTgt,
                             JSHandle<JSTaggedValue> preArgs, uint32_t argsCount, JSTaggedType *stkargs)
{
    // step 1 ~ 4 get ProxyHandler and ProxyTarget
    JSHandle<JSTaggedValue> handler(thread, ctor->GetHandler());
    if (handler->IsNull()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "Constructor: handler is null", JSTaggedValue::Exception());
    }
    ASSERT(handler->IsJSObject());
    JSHandle<JSTaggedValue> target(thread, ctor->GetTarget());

    // 5.Let trap be GetMethod(handler, "construct").
    JSHandle<JSTaggedValue> key(thread->GlobalConstants()->GetHandledProxyConstructString());
    JSHandle<JSTaggedValue> method = JSObject::GetMethod(thread, handler, key);

    // 6.ReturnIfAbrupt(trap).
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    // 7.If trap is undefined, then
    //   a.Assert: target has a [[Construct]] internal method.
    //   b.Return Construct(target, argumentsList, new_target).
    if (method->IsUndefined()) {
        ASSERT(target->IsConstructor());
        return SlowRuntimeHelper::Construct(thread, target, newTgt, preArgs, argsCount, stkargs);
    }

    // 8.Let argArray be CreateArrayFromList(argumentsList).
    uint32_t preArgsSize = preArgs->IsUndefined() ? 0 : JSHandle<TaggedArray>::Cast(preArgs)->GetLength();
    const uint32_t size = preArgsSize + argsCount;
    JSHandle<TaggedArray> args = thread->GetEcmaVM()->GetFactory()->NewTaggedArray(size);
    if (preArgsSize > 0) {
        JSHandle<TaggedArray> tgaPreArgs = JSHandle<TaggedArray>::Cast(preArgs);
        for (uint32_t i = 0; i < preArgsSize; ++i) {
            JSTaggedValue value = tgaPreArgs->Get(i);
            args->Set(thread, i, value);
        }
    }
    for (ArraySizeT i = 0; i < argsCount; ++i) {
        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
        args->Set(thread, i + preArgsSize, JSTaggedValue(stkargs[i]));
    }

    // step 8 ~ 9 Call(trap, handler, «target, argArray, new_target »).

    auto info = NewRuntimeCallInfo(thread, method, handler, JSTaggedValue::Undefined(), 3);
    info->SetCallArgs(target, JSHandle<JSTaggedValue>(args), newTgt);
    JSTaggedValue newObjValue = JSFunction::Call(info.Get());  // 3: «target, argArray, new_target »
    // 10.ReturnIfAbrupt(newObj).
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    // 11.If Type(newObj) is not Object, throw a TypeError exception.
    if (!newObjValue.IsECMAObject()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "new object is not object", JSTaggedValue::Exception());
    }
    // 12.Return newObj.
    return newObjValue;
}

JSTaggedValue SlowRuntimeHelper::Construct(JSThread *thread, JSHandle<JSTaggedValue> ctor,
                                           JSHandle<JSTaggedValue> newTarget, JSHandle<JSTaggedValue> preArgs,
                                           uint32_t argsCount, JSTaggedType *stkargs)
{
    if (newTarget->IsUndefined()) {
        newTarget = ctor;
    }

    if (!(newTarget->IsConstructor() && ctor->IsConstructor())) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "Constructor is false", JSTaggedValue::Exception());
    }
    if (ctor->IsJSFunction()) {
        return ConstructGeneric(thread, JSHandle<JSFunction>::Cast(ctor), newTarget, preArgs, argsCount, stkargs);
    }
    if (ctor->IsBoundFunction()) {
        return ConstructBoundFunction(thread, JSHandle<JSBoundFunction>::Cast(ctor), newTarget, preArgs, argsCount,
                                      stkargs);
    }
    if (ctor->IsJSProxy()) {
        return ConstructProxy(thread, JSHandle<JSProxy>::Cast(ctor), newTarget, preArgs, argsCount, stkargs);
    }
    THROW_TYPE_ERROR_AND_RETURN(thread, "Constructor NonConstructor", JSTaggedValue::Exception());
}

JSTaggedValue SlowRuntimeHelper::FindPrivateKey(JSThread *thread, const JSHandle<JSConstructorFunction> &privateContext,
                                                const JSHandle<JSObject> &obj, const JSHandle<JSTaggedValue> &key,
                                                PropertyDescriptor &propDesc, PrivateFieldKind &kind)
{
    JSHandle<TaggedArray> desc(thread, privateContext->GetPrivateFields());

    for (ArraySizeT i = 0; i < desc->GetLength();) {
        JSHandle<JSTaggedValue> kindValue(thread, desc->Get(thread, i++));
        kind = static_cast<PrivateFieldKind>(kindValue->GetInt());
        JSHandle<JSSymbol> privateSymbol(thread, desc->Get(thread, i++));

        switch (kind) {
            case PrivateFieldKind::FIELD:
            case PrivateFieldKind::STATIC_FIELD: {
                break;
            }
            default: {
                i++;
            }
        }

        if (!JSTaggedValue::Equal(thread, JSHandle<JSTaggedValue>(thread, privateSymbol->GetDescription()), key)) {
            continue;
        }

        bool exists = JSObject::GetOwnProperty(thread, obj, JSHandle<JSTaggedValue>::Cast(privateSymbol), propDesc);

        if (!exists) {
            propDesc.SetValue(JSHandle<JSTaggedValue>(thread, JSTaggedValue::Hole()));
        }

        return privateSymbol.GetTaggedValue();
    }

    propDesc.SetValue(JSHandle<JSTaggedValue>(thread, JSTaggedValue::Hole()));
    return JSTaggedValue::Undefined();
}

}  // namespace ark::ecmascript
